// 导入TensorFlow.js可视化库，用来画图展示数据和训练过程
import * as tfvis from '@tensorflow/tfjs-vis';
// 导入TensorFlow.js核心库，用来构建和训练神经网络
import * as tf from '@tensorflow/tfjs';
// 导入咱们刚才写的数据生成函数
import { getData } from './data.js';

// 生成400个数据点，这些点会按照XOR模式分布
const data = getData(400)

// 页面加载完成后执行的代码
window.onload = async () => {
    // 用tfvis库画一个散点图，展示我们的训练数据
    tfvis.render.scatterplot(
        { name: 'XOR 训练数据' },  // 图表名称
        {
            // 把数据分成两组：标签为0的点和标签为1的点，方便在图上用不同颜色显示
            values: [
                data.filter(p => p.label === 0),  // 筛选出标签为0的点
                data.filter(p => p.label === 1)   // 筛选出标签为1的点
            ]
        },
    )
    
    // 创建一个序列模型（最常用的神经网络结构）
    const model = tf.sequential()
    
    // 添加第一个隐藏层：全连接层，有4个神经元，使用ReLU激活函数
    // inputShape[2]表示每个输入样本有2个特征（x坐标和y坐标）
    model.add(tf.layers.dense({
        units: 4,            // 神经元数量
        activation: 'relu',  // 激活函数，ReLU可以帮助模型学习非线性特征
        inputShape: [2]      // 输入形状，这里是2维输入（x和y坐标）
    }))
    
    // 添加输出层：全连接层，只有1个神经元，使用sigmoid激活函数
    // sigmoid函数可以把输出值压缩到0到1之间，适合二分类问题
    model.add(tf.layers.dense({
        units: 1,            // 输出层只有1个神经元
        activation: 'sigmoid'  // sigmoid激活函数，输出0~1之间的概率值
    }))
    
    // 编译模型，设置损失函数和优化器
    model.compile({
        loss: tf.losses.logLoss,     // 使用对数损失函数，适合二分类问题
        optimizer: tf.train.adam(0.1)  // 使用Adam优化器，学习率设为0.1
    })
    
    // 准备训练数据：把JavaScript数组转换成TensorFlow张量
    // inputs是特征数据，每个样本是[x, y]坐标
    const inputs = tf.tensor(data.map(p => [p.x, p.y]))
    // labels是标签数据，每个样本是0或1
    const labels = tf.tensor(data.map(p => p.label))
    
    // 训练模型
    model.fit(inputs, labels, {
        epochs: 10,  // 训练10轮
        callbacks: tfvis.show.fitCallbacks(
            { name: '训练进度' },  // 进度图名称
            ['loss']  // 要显示的指标：损失值
        )
    })
    
    // 定义一个全局的预测函数，供HTML页面上的表单调用
    window.predict = async (form) => {
        // 获取表单中输入的x和y值，转换成张量格式
        // 这里的 -0 是为了确保值被转换成数字类型（防止字符串）
        const result = model.predict(tf.tensor([[form.x.value * 1, form.y.value * 1]]))
        // 从预测结果中获取具体数值并弹出提示框显示
        // dataSync()会同步获取张量中的数据
        alert(result.dataSync()[0])
    }
}
